#pragma once

#include <vex/core/contract/memory_resource.h>
#include <utility>
#include <cstdlib>
#include <type_traits>
#include <new>

namespace vex {
    namespace core {
        namespace implementation {

            /// <summary>
            /// Implements <code>contracts::memory_resource</code> 
            /// </summary>
            /// <param name="TAlloc">
            /// A model of a simple allocator =  much less than a full fledged 
            /// allocator with only public allocate and deallocate methods
            /// </param>
            template<class TAlloc>
            class VEX_ABSTRACT allocator_memory_resource
                : public contract::memory_resource
            {
                mutable TAlloc m_alloc;
                allocator_memory_resource(allocator_memory_resource const&);
                allocator_memory_resource& operator=(allocator_memory_resource const&);

                VEX_PROTECT_CONTRACT(allocator_memory_resource);
            public:
                // this is the equivalent of std::pmr::resource_adaptor_imp but I have no clue how
                // this could be implemented in a platform/toolset independent way. If you ask me,
                // it can't
                virtual void* allocate(size_t p_size, size_t p_alignment, void const* p_hint) const
                {
                    try {
                        // this allocator can only allocate allocator_traits::value.
                        // how is this supposed to staify the request for just p_size bytes of memory?
                        return m_alloc.allocate(p_size, p_hint);
                    } catch (...) {
                        return nullptr;
                    }
                }

                virtual void deallocate(void *p_memory, size_t p_size, size_t /*p_alignment*/) const
                {
                    return m_alloc.deallocate(p_memory, p_size);
                }

                template<class UAlloc>
                allocator_memory_resource(UAlloc&& p_alloc, typename std::enable_if<
                    std::is_convertible<UAlloc, TAlloc>::value>::type* = nullptr)
                    : m_alloc(std::forward<UAlloc>(p_alloc))
                {
                }

                allocator_memory_resource(allocator_memory_resource&& p_other)
                    : m_alloc(std::move(p_other.m_alloc))
                {
                }

                allocator_memory_resource()
                {
                }
            };

            /// <summary>
            /// Model instance of simple allocator. Uses <code>std::malloc</code> and 
            /// <code>std::free</code> to manage free store memory.
            /// </summary>
            struct cstdlib_allocator {
                void* allocate(size_t p_size, void const* /*p_hint*/ = 0) const
                {
                    return std::malloc(p_size);
                }

                void deallocate(void *p_memory, size_t /*p_size*/) const
                {
                    return std::free(p_memory);
                }
            };

            typedef allocator_memory_resource <
                cstdlib_allocator
            > cstdlib_memory_resource;
        }
    }
}